
/*======================= override functions =======================*/

/**
 * creates a cross browser compliant XmlHttpRequest object
 */
XmlHttp.create = function() {
	try {
		var xhr;
		if(window.XMLHttpRequest) {
			xhr = new XMLHttpRequest();
			if(!("withCredentials" in xhr)) {
				xhr = new CrossXHR();
			}
		} else {
			xhr = new CrossXHR();
		}
		return xhr;
	} catch (ex) {
		// fell through
		throw new Error("Your browser does not support XmlHttp objects");
	}
}


/**
 * Creates a new Cookie
 * @class Class representing browser cookies for storing small amounts of data
 * @constructor
 * @param {String} name   The name of the value to store
 * @param {String} value  The value to store
 * @param {int}    secs   Number of seconds until cookie expires (may be empty)
 * @param {String} domain The domain for the cookie
 * @param {String} path   The path of cookie
 */
function JSJaCCookie(name, value, secs, domain, path) {
	if(window == this)
		return new JSJaCCookie(name, value, secs, domain, path);

	/**
	 * This cookie's name
	 * @type String
	 */
	this.name = name;
	/**
	 * This cookie's value
	 * @type String
	 */
	this.value = value;
	/**
	 * Time in seconds when cookie expires (thus being delete by
	 * browser). A value of -1 denotes a session cookie which means that
	 * stored data gets lost when browser is being closed.
	 * @type int
	 */
	this.secs = secs;

	/**
	 * The cookie's domain
	 * @type string
	 */
	this.domain = domain;

	/**
	 * The cookie's path
	 * @type string
	 */
	this.path = path;

	/**
	 * Stores this cookie
	 */
	this.write = function() {
		var expires;
		if(this.secs) {
			var date = new Date();
			date.setTime(date.getTime() + (this.secs * 1000));
			expires = "; expires=" + date.toGMTString();
		} else
			expires = "";
		var domain = this.domain ? "; domain=" + this.domain : "";
		var path = this.path ? "; path=" + this.path : "; path=/";
		document.cookie = this.getName() + "=" + JSJaCCookie._escape(this.getValue()) + expires + domain + path;
	};
	/**
	 * Deletes this cookie
	 */
	this.erase = function() {
		var c = new JSJaCCookie(this.getName(), "", -1, this.domain);
		c.write();
	};
	/**
	 * Gets the name of this cookie
	 * @return The name
	 * @type String
	 */
	this.getName = function() {
		return this.name;
	};
	/**
	 * Sets the name of this cookie
	 * @param {String} name The name for this cookie
	 * @return This cookie
	 * @type Cookie
	 */
	this.setName = function(name) {
		this.name = name;
		return this;
	};
	/**
	 * Gets the value of this cookie
	 * @return The value
	 * @type String
	 */
	this.getValue = function() {
		return this.value;
	};
	/**
	 * Sets the value of this cookie
	 * @param {String} value The value for this cookie
	 * @return This cookie
	 * @type Cookie
	 */
	this.setValue = function(value) {
		this.value = value;
		return this;
	};
	/**
	 * Sets the domain of this cookie
	 * @param {String} domain The value for the domain of the cookie
	 * @return This cookie
	 * @type Cookie
	 */
	this.setDomain = function(domain) {
		this.domain = domain;
		return this;
	};
	/**
	 * Sets the path of this cookie
	 * @param {String} path The value of the path of the cookie
	 * @return This cookie
	 * @type Cookie
	 */
	this.setPath = function(path) {
		this.path = path;
		return this;
	};
}
/**
 * Reads the value for given <code>name</code> from cookies and return new
 * <code>Cookie</code> object
 * @param {String} name The name of the cookie to read
 * @return A cookie object of the given name
 * @type Cookie
 * @throws CookieException when cookie with given name could not be found
 */
JSJaCCookie.read = function(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i = 0; i < ca.length; i++) {
		var c = ca[i];
		while(c.charAt(0) == ' ')
		c = c.substring(1, c.length);
		if(c.indexOf(nameEQ) == 0)
			return new JSJaCCookie(name, JSJaCCookie._unescape(c.substring(nameEQ.length, c.length)));
	}
	throw new JSJaCCookieException("Cookie not found");
};
/**
 * Reads the value for given <code>name</code> from cookies and returns
 * its valued new
 * @param {String} name The name of the cookie to read
 * @return The value of the cookie read
 * @type String
 * @throws CookieException when cookie with given name could not be found
 */
JSJaCCookie.get = function(name) {
	return JSJaCCookie.read(name).getValue();
};
/**
 * Deletes cookie with given <code>name</code>
 * @param {String} name The name of the cookie to delete
 * @throws CookieException when cookie with given name could not be found
 */
JSJaCCookie.remove = function(name) {
	JSJaCCookie.read(name).erase();
};
/**
 * @private
 */
JSJaCCookie._escape = function(str) {
	return str.replace(/;/g, "%3AB");
}
/**
 * @private
 */
JSJaCCookie._unescape = function(str) {
	return str.replace(/%3AB/g, ";");
}


/**
 * Resumes this connection from saved state (cookie)
 * @conditionsObj {bareJid:''} 作为判断是否要resume的条件，可为空. for example: {bareJid:'tom@webim.com'}
 * @return Whether resume was successful
 * @type boolean
 */
JSJaCConnection.prototype.resume = function(cookieDomain, conditionsObj) {
	try {
		var cookie = JSJaCCookie.read(this._cookie_prefix + 'JSJaC_State');
		var json = JSJaCJSON.parse(cookie.getValue());
		cookie.setDomain(cookieDomain);
		cookie.erase();
		
		//判断是否需要resume
		if (json._httpbase != this._httpbase || (conditionsObj && json.jid != conditionsObj.bareJid)) {
			return false;
		}
		
		return this.resumeFromData(json);
	} catch (e) {
	}
	return false;
	// sth went wrong
};

/**
 * @private
 * 1.解决原来账号密码正确却登录不了openfire的BUG
 * 2.解决支持登录账号带"@". 带"@"的账号connect时要先escapeNode
 */
JSJaCConnection.prototype._doSASLAuthDigestMd5S1 = function(el) {
	if(el.nodeName != "challenge") {
		this.oDbg.log("challenge missing", 1);
		this._handleEvent('onerror', JSJaCError('401', 'auth', 'not-authorized'));
		this.disconnect();
	} else {
		var challenge = b64decode(el.firstChild.nodeValue);
		this.oDbg.log("got challenge: " + challenge, 2);
		this._nonce = challenge.substring(challenge.indexOf("nonce=") + 7);
		this._nonce = this._nonce.substring(0, this._nonce.indexOf("\""));
		this.oDbg.log("nonce: " + this._nonce, 2);
		if(this._nonce === '' || this._nonce.indexOf('\"') != -1) {
			this.oDbg.log("nonce not valid, aborting", 1);
			this.disconnect();
			return;
		}

		this._digest_uri = "xmpp/";
		this._digest_uri += this.domain;

		this._cnonce = cnonce(14);

		this._nc = '00000001';

		var A1 = str_md5(this.username + ':' + this.domain + ':' + this.pass) + ':' + this._nonce + ':' + this._cnonce;

		var A2 = 'AUTHENTICATE:' + this._digest_uri;

		var response = hex_md5(hex_md5(A1) + ':' + this._nonce + ':' + this._nc + ':' + this._cnonce + ':auth:' + hex_md5(A2));

		var rPlain = 'username=' + this._quote(this.username) + ',realm=' + this._quote(this.domain) + ',nonce=' + this._quote(this._nonce) + ',cnonce=' + this._quote(this._cnonce) + ',nc=' + this._quote(this._nc) + ',qop=auth,digest-uri=' + this._quote(this._digest_uri) + ',response=' + this._quote(response) + ',charset="utf-8"' + ' '; //注意最后面加一个空格是为了解决openfire验证时报的异常:javax.security.sasl.SaslException: Expecting comma or linear whitespace after quoted string: "utf-8"

		this.oDbg.log("response: " + rPlain, 2);

		this._sendRaw("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + binb2b64(str2binb(rPlain)) + "</response>", this._doSASLAuthDigestMd5S2);
	}
};

/**
 * Suspends this connection (saving state for later resume)
 * Saves state to cookie
 * @return Whether suspend (saving to cookie) was successful
 * @type boolean
 */
JSJaCConnection.prototype.suspend = function(domain) {
	var data = this.suspendToData();

	try {
		var c = new JSJaCCookie(this._cookie_prefix + 'JSJaC_State', JSJaCJSON.toString(data), null, domain);
		this.oDbg.log("writing cookie: " + c.getValue() + "\n" + "(length:" + c.getValue().length + ")", 2);
		c.write();

		var c2 = JSJaCCookie.get(this._cookie_prefix + 'JSJaC_State');
		if(c.getValue() != c2) {
			this.oDbg.log("Suspend failed writing cookie.\nread: " + c2, 1);
			c.erase();
			return false;
		}
		return true;
	} catch (e) {
		this.oDbg.log("Failed creating cookie '" + this._cookie_prefix + "JSJaC_State': " + e.message, 1);
	}
	return false;
};

/*======================= add functions =======================*/

/** Function: escapeNode
 *  Escape the node part (also called local part) of a JID.
 *
 *  Parameters:
 *    (String) node - A node (or local part).
 *
 *  Returns:
 *    An escaped node (or local part).
 */
JSJaC.escapeNode = function(node) {
	return node.replace(/^\s+|\s+$/g, '').replace(/\\/g, "\\5c").replace(/ /g, "\\20").replace(/\"/g, "\\22").replace(/\&/g, "\\26").replace(/\'/g, "\\27").replace(/\//g, "\\2f").replace(/:/g, "\\3a").replace(/</g, "\\3c").replace(/>/g, "\\3e").replace(/@/g, "\\40");
};

/** Function: unescapeNode
 *  Unescape a node part (also called local part) of a JID.
 *
 *  Parameters:
 *    (String) node - A node (or local part).
 *
 *  Returns:
 *    An unescaped node (or local part).
 */
JSJaC.unescapeNode = function(node) {
	return node.replace(/\\20/g, " ").replace(/\\22/g, '"').replace(/\\26/g, "&").replace(/\\27/g, "'").replace(/\\2f/g, "/").replace(/\\3a/g, ":").replace(/\\3c/g, "<").replace(/\\3e/g, ">").replace(/\\40/g, "@").replace(/\\5c/g, "\\");
};

/** Function: getNodeFromJid
 *  Get the node portion of a JID String.
 *
 *  Parameters:
 *    (String) jid - A JID.
 *
 *  Returns:
 *    A String containing the node.
 */
JSJaC.getNodeFromJid = function (jid) {
    if (jid.indexOf("@") < 0) { return null; }
    return jid.split("@")[0];
};

/** PrivateFunction: _quote
 *  _Private_ utility function to backslash escape and quote strings.
 *
 *  Parameters:
 *    (String) str - The string to be quoted.
 *
 *  Returns:
 *    quoted string
 */
JSJaCConnection.prototype._quote = function (str){
    return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
};









